#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         Core.py
#
# Description:  Core provides base services for the Knowledge
#               Engine framework.  This includes;
#
#               1). Maintaining a universal in-memory catalog of the
#               registered KnowledgeEngine GenericObjects ClassInfo
#               instances to provide runtime reflection/introspection
#               services.
#
#               2). Maintaining a Editlet (widget like) catalog of
#               objects that provide property form editing services.
#
#               3). Standardised integration services with the 
#               Zope/CMF/Plone framework
#
# Note:         This is a module. It is initialised when the 
#               Knowledge Engine is loaded or refreshed
#               (bootstraped)
#
# Copyright:    Copyright (c) 2005, Faulkner Technologies
#
# Author:       Brian Oliver
#
#####################################################################

#####################################################################
## KnowledgeEngine Library Imports
from Common import *

#####################################################################
## CMF Library Imports
from Products.CMFCore.utils import getToolByName

#####################################################################
## Zope Library Imports
from Globals import InitializeClass
from Globals import DTMLFile
from App.FindHomes import ppath
from DateTime import DateTime

#####################################################################
## Python Library Imports
from time import asctime, localtime
import sys, os

#####################################################################
## Python Logging Configuration
import logging
log = logging.getLogger("KnowledgeEngine.Core")

#####################################################################

#the catalog of 'classname' -> ClassInfo references
classInfoCatalog = {}

#the catalog of 'editlet' -> PropertyEditlet references
editletCatalog = {}

#the catalog of 'accessory' -> Accessory references
accessoryCatalog = {}

#the catalog of 'service' -> Service references
serviceInfoCatalog = {}

#the catalog of 'classname' -> (Class, ClassInfo, ConstructorMethod, FactoryTypeInfo) for portal objects 
#(those that can be placed in a portal without being in a GenericObject containerish object)
portalInfoCatalog = {}

#the version of the engine (initialised on load)
version = "4.0.0"

#the DateTime the engine was booted
bootTime = None

#the maximum assessment checkout time (in minutes)
maximumAssessmentCheckoutTime = 20

#---------------------------------------------------------------#

def onBeforeStart():
    """
        Perform the pre-initialisation of the catalog, before
        the Knowledge Engine loads other components.
        
        This is during the bootstrap process.  This method is
        called by __init__.py for the Knowledge Engine product.
    """
    
    #determine the version of the product
    for product_dir in ppath:
        PRODUCT_PATH = product_dir + os.sep + "KnowledgeEngine"
        if os.path.exists(PRODUCT_PATH):
            for version_file_name in ('VERSION.TXT', 'VERSION.txt', 'version.txt'):
                version_file = PRODUCT_PATH + os.sep + version_file_name
                if os.path.exists(version_file):
                    file = open(version_file, 'r')
                    version = file.readline().strip()
                    file.close()
                    break    
    
    #determine the start time
    bootTime = DateTime()
    
    #output a nice banner containing some version information
    log.info("------------------------------------------------------------------------------")
    log.info("  KnowledgeEngine (Plone Edition) [v%s] " % version)
    log.info("                                                                              ")
    log.info("  Copyright 2006. All Rights Reserved. Faulkner Technologies                  ")
    log.info("                                                                              ")
    log.info("  http://www.faulknertechnologies.com                                         ")
    log.info("  mailto:sales@faulknertechnologies.com                                       ")
    log.info("------------------------------------------------------------------------------")
    log.info("Commence: Bootstrap Process at %s " % (asctime(localtime())))
    
    #establish a new catalog of references to ClassInfo instances
    classInfoCatalog = {}    

    #establish a new catalog of references to Editlet instances
    editletCatalog = {}

    #establish a new catalog of references to Accessory instances
    accessoryCatalog = {}

    #establish a new catalog of references to Service instances
    serviceInfoCatalog = {}
    
    #establish a new catalog of references to Portal object information (for Zope really)
    portalInfoCatalog = {}
    
#---------------------------------------------------------------#

def onAfterStart():
    """
        Perform the post-initialisation of the catalog, after
        the Knowledge Engine loads other components.
        
        This is during the bootstrap process.  This method is
        called by __init__.py for the Knowledge Engine product.
    """
    
    ## Call the ClassInfo.onAfterStartup() method for each registered class
    ## to allow them to perform custom initialization
    for classInfo in classInfoCatalog.values():    
        classInfo.onAfterStartup()
    
    log.info("Complete: Bootstrap Process - Engine Ready (%d classes registered)" % (len(classInfoCatalog),))
    
#---------------------------------------------------------------#

def getVersion():
    """
        Return the current version of the Knowledge Engine
    """
    
    return version
    
#---------------------------------------------------------------#

def getBootTime():
    """
        Return a DateTime object that represents the time
        the knowledge engine was last booted/restarted
    """
    
    return bootTime
    
#---------------------------------------------------------------#

def getMaximumAssessmentCheckoutTime():
    """
        Returns the maximum assessment checkout time (in minutes)
    """
    
    return maximumAssessmentCheckoutTime
    
#---------------------------------------------------------------#

def registerClassInfo(classInfo, classReference):
    """
        Register the specified ClassInfo instance for the 
        specified classReference in the catalog
    """

    #update the class reference in the classInfo instance
    classInfo.classReference = classReference   

    #set the Zope meta_type
    classInfo.getClass().meta_type = classInfo.getDisplayName()
    
    #set the Zope icon
    classInfo.getClass().icon = "misc_/KnowledgeEngine/%s.gif" % (classInfo.getClassName(),)

    #set the class identity_type
    classInfo.getClass().identity_type = classInfo.getIdentityType()

    #add the classInfo to the catalog
    classInfoCatalog.update({classInfo.getClassName():classInfo})
    
    #configure the class properties for PropertyReflection
    classInfo.onConfigureProperties()

    #configure the class events for EventReflection
    classInfo.onConfigureEvents()
    
    #configure the class permission definitions
    classInfo.onConfigurePermissions()
    
#---------------------------------------------------------------#

def registerGenericObjectClass(keClass):
    """
        Register the specified KnowledgeEngine GenericObject sub/class
        in the catalog.
    """

    #register the class information
    registerClassInfo(keClass._CLASSINFO, keClass)

    #initialise the class security with Zope
    InitializeClass(keClass)
    
#---------------------------------------------------------------#

def registerPortalObjectClass(keClass, addMethod, factoryTypeInformation):
    """
        Register the specified KnowledgeEngine GenericObject class
        as a Portal object
    """

    #register the class information
    registerGenericObjectClass(keClass)

    #add the information to the portal catalog
    portalInfoCatalog.update({keClass._CLASSINFO.getClassName():(keClass, keClass._CLASSINFO, addMethod, factoryTypeInformation)})    
    
#---------------------------------------------------------------#

def getPortalObjectRegistrations():
    """
        Returns a list of (keClass, classInfo, addMethod, factoryTypeInformation) for each of the 
        registered portal object classes
    """
    
    return portalInfoCatalog.values()
    
#---------------------------------------------------------------#

def registerServiceClass(keClass, addDTMLForm = None, addMethod = None):
    """
        Register the specified KnowledgeEngine Service sub/class
    """

    #register the class information
    serviceInfo = keClass._SERVICEINFO

    #update the class reference in the classInfo instance
    serviceInfo.classReference = keClass   

    #set the Zope meta_type
    serviceInfo.getClass().meta_type = serviceInfo.getDisplayName()
    
    #set the Zope icon
    serviceInfo.getClass().icon = "misc_/KnowledgeEngine/%s.gif" % (serviceInfo.getClassName(),)
    
    #set the Zope id
    serviceInfo.getClass().id = serviceInfo.getServiceName()

    #add the serviceInfo to the catalog
    serviceInfoCatalog.update({serviceInfo.getClassName():serviceInfo})

    #initialise the class security with Zope
    InitializeClass(keClass)
                            
#---------------------------------------------------------------#

def getClassInfo(className):
    """
        Returns the ClassInfo instance for the specified className
        Returns None if no such registered ClassInfo for className
    """
    
    #print "Catalog: " + `classInfoCatalog`
    
    if classInfoCatalog.has_key(className):
        return classInfoCatalog[className]
    else:
        return None
                            
#---------------------------------------------------------------#

def getServiceInfo(serviceName):
    """
        Returns the ServiceInfo instance for the specified serviceName
        Returns None if no such registered ServiceInfo for serviceName
    """
    
    if serviceInfoCatalog.has_key(serviceName):
        return serviceInfoCatalog[serviceName]
    else:
        return None

#---------------------------------------------------------------#

def getRegisteredServiceInfo():
    """
        Return a list of ServiceInfo objects that have been registered
    """
    
    return serviceInfoCatalog.values()
    
#---------------------------------------------------------------#

def getPermittedChildren(containerClassInfo, containerInstance = None, sortDisplayNames = false):
    """
        Return the list of ClassInfo instances for children
        that are permitted as children the specified container class
    """
    
    #an empty list of classInfo
    result = []
    
    #enumerate over all of the registered classes
    for className in classInfoCatalog.keys():
        
        #get the class info for the className
        classInfo = getClassInfo(className)
        
        #is the class permitted in the container and the container permits the class?
        if classInfo.isPermittedInContainer(containerClassInfo, containerInstance) and containerClassInfo.isChildPermitted(classInfo):
            result = result + [classInfo]
          
    #sort the classes based on the display names?
    if sortDisplayNames:
        result.sort(lambda x, y : -(x.getDisplayName() < y.getDisplayName()))        

    return result

#---------------------------------------------------------------#

def getRegisteredClassInfo():
    """
        Return a list of classInfo objects that have been registered
    """
    
    return classInfoCatalog.values()

#---------------------------------------------------------------#

def registerEditlet(editletName, editlet):
    """
        Register the specified Editlet instance in the 
        Editlet Catalog with the specified name
    """

    #add the editlet to the catalog
    editletCatalog.update({editletName:editlet})

#---------------------------------------------------------------#

def getRegisteredEditlets():
    """
        Return a list of Editlet objects that have been registered
    """
    
    return editletCatalog.values()
                            
#---------------------------------------------------------------#

def getEditlet(editletName):
    """
        Returns the Editlet instance for the specified editletName
        Returns None if no such registered Editlet for editletName
    """
    
    if editletCatalog.has_key(editletName):
        return editletCatalog[editletName]
    else:
        return None
    
#---------------------------------------------------------------#

def getEditletFor(propertyDefinition, propertyAccess=None, value=None, isForConstructor=false, REQUEST=None, context=None):
    """
        Negotiates and returns the first appropriate Editlet from the
        catalog of registered Editlets suitable for editing the
        specified property and object/value.
       
        Returns None if a suitable editlet can not be found.

        PARAMETERS:

        propertyDefinition  The property definition for which 
                            an editlit is required.
                            
        propertyAccess      (Optional) An object implementing
                            the PropertyAccess interface so the said
                            property value can be used to determine
                            editlets.  Default is None.
                            
        value               (Optional) The value of the property 
                            to edit (if the propertyAccess isn't 
                            available).  Optionally used to determine
                            the editlet for the property and value.
                            Default is None.
        
        isForConstructor    (Optional) Is the Editlet for the an
                            object constructor.  Default is false.

        REQUEST             (Optional) The Request Object from Zope, 
                            providing information about the context 
                            in which the editlet is required.  
                            Default is None.
                            
        context             (Optional) The context (usually the parent)
                            of the object property being edited.                            
                            Default is None.
    """
    
    #find the appropriate editlets
    editlets = [editlet for editlet in getRegisteredEditlets() if editlet.isEditable(propertyDefinition, propertyAccess, value, isForConstructor, REQUEST, context) and ((isForConstructor and propertyDefinition.requiresConstructor()) or (not isForConstructor and propertyDefinition.requiresPropertyEditor()))]
    
    #are their any appropriate editlets
    if len(editlets) > 0:
        #return the first editlet
        return editlets[0]
    else:
        #no appropriate editlets
        return None

#---------------------------------------------------------------#

def registerAccessory(accessory):
    """
        Register the specified Accessory instance in the 
        Accessory Catalog
    """

    #add the accessory to the catalog
    accessoryCatalog.update({accessory.getIdentity():accessory})

#---------------------------------------------------------------#

def getRegisteredAccessories():
    """
        Return a list of Accessory objects that have been registered
    """
    
    return accessoryCatalog.values()
                            
#---------------------------------------------------------------#

def getAccessory(accessoryIdentity):
    """
        Returns the Accessory instance for the specified accessoryIdentity
        Returns None if no such registered Accessory for accessoryIdentity
    """
    
    if accessoryCatalog.has_key(accessoryIdentity):
        return accessoryCatalog[accessoryIdentity]
    else:
        return None
    
#---------------------------------------------------------------#

def getAssessmentStorageService(portalObject):
    """
        Returns the assessment storage service that is used
        to manage assessments within the specified contextObject.
        
        NOTE: The portalObject can be any object within a portal.
        This includes, GenericObjects and Services.  It is used
        to lookup the assessment storage service via acquisition.
    """

    #we use the CMF tool lookup method to find the installed service
    return getToolByName(portalObject, "knowledgeengine_assessmentstorageservice")
    
#---------------------------------------------------------------#

def getCreditManagementService(portalObject):
    """
        Returns the credit management service that is used
        to manage knowledge engine permission, securable and actor
        credits.
        
        NOTE: The portalObject can be any object within a portal.
    """

    #we use the CMF tool lookup method to find the installed service
    return getToolByName(portalObject, "knowledgeengine_creditmanagementservice")
    
#---------------------------------------------------------------#

def getSecurityService(portalObject):
    """
        Returns the security service that is used
        to manage knowledge engine object security
        
        NOTE: The portalObject can be any object within a portal.
        This includes, GenericObjects and Services.  It is used
        to lookup the assessment storage service via acquisition.
    """

    #we use the CMF tool lookup method to find the installed service
    return getToolByName(portalObject, "knowledgeengine_securityservice")
    
#---------------------------------------------------------------#

def getUserProviderService(portalObject):
    """
        Returns the user provider service that is used
        to manage knowledge engine users
        
        NOTE: The portalObject can be any object within a portal.
        This includes, GenericObjects and Services.  It is used
        to lookup the assessment storage service via acquisition.
    """

    #we use the CMF tool lookup method to find the installed service
    return getToolByName(portalObject, "knowledgeengine_userproviderservice")
    
#---------------------------------------------------------------#

def getGroupProviderService(portalObject):
    """
        Returns the group provider service that is used
        to manage knowledge engine groups
        
        NOTE: The portalObject can be any object within a portal.
        This includes, GenericObjects and Services.  It is used
        to lookup the assessment storage service via acquisition.
    """

    #we use the CMF tool lookup method to find the installed service
    return getToolByName(portalObject, "knowledgeengine_groupproviderservice")
    
#---------------------------------------------------------------#
  